package primary;

import custom_class.Person;

import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.io.*;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Scanner;
import java.util.function.Consumer;

/**
 * @author wenyuan
 * @version 1.0
 * @date 2023/8/2
 * @since 8
 */
public class FileIO {

    private static String relativePath1;

    public static void main(String[] args) throws Exception {
        // 1. 创建文件
        // recognizeCreateFile();

        // 2. 文件流
        // recognizeFileStream();

        // 3. 删除文件 文件夹
        // recognizeDeleteFile();

        // 4. 文件的相关信息（重命名文件 移动文件 等）
        // recognizeFileInfoAround();

        // 5. 其他关于文件的操作
        otherOperation();

    }

    /**
     * note
     * <p>
     *
     * </p>
     */
    private static void otherOperation() throws Exception {
        String fileSeparator = System.getProperty("file.separator");
        String relativePath1 = "io" + fileSeparator + "src" + fileSeparator + "primary" + fileSeparator + "text"
                + fileSeparator + "卡莎.txt";
        String relativePath2 = "io" + fileSeparator + "src" + fileSeparator + "primary" + fileSeparator + "text"
                + fileSeparator + "网络资源.html";
        // // 1. 根据文件关联的应用程序来打开文件
        // File file = new File(relativePath1);
        // // 检查 Desktop 是否被系统支持
        // if (!Desktop.isDesktopSupported()) {
        //     System.out.println("Desktop is note supported");
        //     return;
        // }
        // Desktop desktop = Desktop.getDesktop();
        // if (file.exists())
        //     desktop.open(file);

        // 2. 使用URL从网络下载文件
        // String url = "https://www.baidu.com";
        // // // 2.1. 使用普通Stream
        // // downloadUsingStream(url, relativePath2);
        // // 2.2. 使用非阻塞NIO
        // downloadUsingNIO(url, relativePath2);

        // 3. 编译和运行另一个Java程序
        runProcess("javac -encoding UTF-8 D:\\backproject\\mix-code-learn\\io\\src\\primary\\Test.java");
        runProcess("java  UTF-8 D:\\backproject\\mix-code-learn\\io\\src\\primary\\Test");
    }

    private static void runProcess(String command) throws Exception {
        Process pro = Runtime.getRuntime().exec(command);
        printLines(command + " stdout:", pro.getInputStream());
        printLines(command + " stderr:", pro.getErrorStream());
        pro.waitFor();
        System.out.println(command + " exitValue()" + pro.exitValue());
    }

    private static void printLines(String cmd, InputStream ins) throws Exception {
        String line = null;
        BufferedReader in = new BufferedReader(new InputStreamReader(ins));
        while ((line = in.readLine()) != null)
            System.out.println(cmd + " " + line);
    }

    /**
     * 使用非阻塞NIO
     */
    private static void downloadUsingNIO(String urlStr, String file) throws IOException {
        URL url = new URL(urlStr);
        ReadableByteChannel rbc = Channels.newChannel(url.openStream());
        FileOutputStream fos = new FileOutputStream(file);
        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
        fos.close();
        rbc.close();
    }

    /**
     * 使用Stream
     */
    private static void downloadUsingStream(String urlStr, String file) throws IOException {
        URL url = new URL(urlStr);
        BufferedInputStream bis = new BufferedInputStream(url.openStream());
        FileOutputStream fos = new FileOutputStream(file);
        byte[] buffer = new byte[1024];
        int count = 0;
        while ((count = bis.read(buffer, 0, 1024)) != -1)
            fos.write(buffer, 0, count);
        fos.close();
        bis.close();
    }

    /**
     * note
     * <p>
     * 1. 可以发现，renameTo 既是重命名文件，也是移动文件的方法
     * 2. 在 File 里没有发现有得到文件大小的方法，只有 nio.Files 工具类里有
     * 3. jdk里没有提供得到后缀名的方法，不过这并不来，自己实现即可
     * </p>
     */
    private static void recognizeFileInfoAround() throws IOException {
        String fileSeparator = System.getProperty("file.separator");
        String relativePath1 = "io" + fileSeparator + "src" + fileSeparator + "primary" + fileSeparator + "text"
                + fileSeparator + "虚空之女.txt";
        String relativePath2 = "io" + fileSeparator + "src" + fileSeparator + "primary" + fileSeparator + "text"
                + fileSeparator + "从虚空回来的女孩.txt";
        String relativePath3 = "io" + fileSeparator + "src" + fileSeparator + "primary"
                + fileSeparator + "从虚空回来的女孩.txt";
        String relativePath4 = "io" + fileSeparator + "src" + fileSeparator + "primary" + fileSeparator + "text";
        File file1 = new File(relativePath1);
        File file2 = new File(relativePath2);
        File file3 = new File(relativePath3);
        File dir = new File(relativePath4);
        // 1. 重命名文件
        // file1.renameTo(file2);

        // 2. 移动文件
        // file3.renameTo(file2);

        // 3. 文件大小
        System.out.println("文件所在分区大小 " + file2.getTotalSpace() + "Bytes");
        System.out.println("文件可用大小 " + file2.getUsableSpace() + "Bytes");
        System.out.println("文件空闲大小 " + file2.getFreeSpace() + "Bytes");
        System.out.println("文件占用大小 " + Files.size(Paths.get(relativePath2)) + "Bytes");

        // 4. 文件扩展名
        String[] list = dir.list();
        for (String path : list) {
            int i = path.lastIndexOf(".");
            System.out.print(path.substring(i + 1) + " ");
        }

        // 5. 文件上次修改时间
        long timestamp = file2.lastModified();
        Timestamp ts = new Timestamp(timestamp);
        LocalDateTime localDateTime = ts.toLocalDateTime();
        System.out.println(localDateTime); // 2023-08-05T22:32:29.943 T是国际规范，在日期和时间合并表示时要加的，也可以自定义输出可以。

        // 6. getCanonicalPath() 会将一些不规范的值擦掉，像 .. \\ 这些不必要的转换成规范的格式
        System.out.println("这个文件路径的规范形式：" + file2.getCanonicalPath());

    }

    /**
     * note
     * <p>
     * 1. delete() 和 deleteOnExit 不同之处在于，前者立即删除，后者是先注册在JVM虚拟机结束时才删除
     * 2. 只有非阻塞NIO包里提供了根据文件路径删除文件，而不用先创建文件对象，不过非阻塞NIO会抛出受检异常IOException需要处理。另外有趣的是，io跟nio两包内的文件系统是不同的
     * 3. 删除文件夹要求该文件夹是空的，如果要删除非空的文件需要自己递归删除
     * </p>
     */
    private static void recognizeDeleteFile() throws IOException {
        String fileSeparator = System.getProperty("file.separator");
        String relativePath1 = "io" + fileSeparator + "src" + fileSeparator + "primary" + fileSeparator + "text"
                + fileSeparator + "虚空之女.txt";
        String relativePath2 = "io" + fileSeparator + "src" + fileSeparator + "primary" + fileSeparator + "text"
                + fileSeparator + "卡莎.txt";
        String relativePath3 = "io" + fileSeparator + "src" + fileSeparator + "primary" + fileSeparator + "text";
        File file1 = new File(relativePath1);
        File file2 = new File(relativePath2);
        File dir = new File(relativePath3);
        // 1. 删除文件
        // 1.1 使用 delete
        // file1.delete();
        // 1.2 使用 deleteOnExit
        // file2.deleteOnExit();
        // 1.3 nio非阻塞IO工具包
        // Files.deleteIfExists(Paths.get(relativePath1));
        // Files.delete(Paths.get(relativePath2));

        // 2. 删除非空文件夹
        // 递归删除
        removeDirRecursively(dir);
    }

    /**
     * 删除非空文件夹或文件
     *
     * @param dir 文件夹
     */
    private static void removeDirRecursively(File dir) {
        // 当前文件夹如果不是个文件夹，直接删除
        if (!dir.isDirectory()) {
            dir.delete();
            return;
        }

        // 当前文件夹如果无文件，直接删除
        File[] files = dir.listFiles();
        if (files.length == 0) {
            dir.delete();
            return;
        }

        // 当前文件夹有文件，进行循环且递归删除
        int i = 0;
        while (i < files.length)
            removeDirRecursively(files[i++]);

        // 文件夹内文件都删完后，删除文件夹
        dir.delete();
    }


    /**
     * note
     * <p>
     * 1. write 是覆盖写，不是追加
     * 2. 输出流所要求的参数 File可以是还未创建的文件，会自动创建
     * 3. Reader结尾 基本单位是读字符的， Stream结尾 基本单位是读字节的
     * </p>
     */
    private static void recognizeFileStream() throws IOException, ClassNotFoundException {
        String fileSeparator = System.getProperty("file.separator");
        String absoluteFilePath = fileSeparator + "backproject" + fileSeparator + "mix-code-learn" + fileSeparator + "io" + fileSeparator + "src" + fileSeparator + "primary" + fileSeparator + "text"
                + fileSeparator + "卡莎.txt";
        Path path = Paths.get(absoluteFilePath);
        String fileData1 = "下次再努力一点，卡莎";
        String fileData2 = "相信自己，卡莎。\n" +
                "每当黑暗将你环绕时就抓住你心底的力量\n" +
                "黑暗的恐惧，给我坚定的勇气";

        // 1. IO
        // FileOutputStream fos = new FileOutputStream(absoluteFilePath);
        // fos.write(fileData1.getBytes());
        // fos.close();

        // 2. NIO 非阻塞IO，不用担心释放资源的问题
        // Files.write(path, fileData2.getBytes());

        // 3. 复制文件，io.File 没有提供拷贝文件的快捷方法
        // 3.1. 个人实现
        // File source = new File(absoluteFilePath);
        // int i = absoluteFilePath.lastIndexOf(fileSeparator);
        // File dest = new File(absoluteFilePath.substring(0, i + 1) + "迅捷斥候.txt");
        // copyFileUsingStream(source, dest);
        // 3.2. 使用 nio.channels.FileChannel 使用这个接口比上面的使用流性能好一些
        // copyFileUsingChannel(source, dest);

        // // 4. 读取文件的4种方式
        // // 4.1. 使用 nio.Files readxxx
        // List<String> strings = Files.readAllLines(path);
        // strings.forEach(System.out::println);
        // System.out.println("----------------------");
        // // 4.2. 使用 FileReader
        // FileReader fr = new FileReader(absoluteFilePath);
        // BufferedReader br = new BufferedReader(fr); // 缓冲读取包装，高效且线程安全，这是强烈建议的
        // String line;
        // while ((line = br.readLine()) != null) {
        //     System.out.println(line);
        // }
        // br.close(); // 别忘了关闭流
        // System.out.println("----------------------");
        // // 4.3. 使用 Scanner 线程不安全，但可以支持正则表达式
        // Scanner scanner = new Scanner(path);
        // System.out.println("使用 Scanner 读取文件");
        // while (scanner.hasNextLine()) {
        //     System.out.println(scanner.nextLine());
        // }
        // scanner.close(); // 别忘了关闭流
        // System.out.println("----------------------");
        // // 4.4. 使用 RandomAccessFile 随即存取文件，有文件指针，可以任意读取
        // RandomAccessFile rcs = new RandomAccessFile(absoluteFilePath, "r");
        // long size = Files.size(path);
        // byte[] bytes = new byte[(int) size];    // 如果不是恰当的大小，会有未知字符出现
        // while ((rcs.read(bytes)) > 1)
        //     System.out.println(new String(bytes, StandardCharsets.UTF_8));
        // rcs.close();

        // 5.1. 将对象写入到文件中，如果有一些属性不想被保存，可以加transient修饰
        // Person person = new Person(170, 120, 21, "男");
        // FileOutputStream fos = new FileOutputStream("./io/src/primary/text/person.txt");
        // ObjectOutputStream oos = new ObjectOutputStream(fos);   // 使用ObjectOutputStream，先要准备一个OutputStream
        // oos.writeObject(person);
        // oos.close();
        // fos.close();
        // 5.2. 将对象从文件中读出来
        FileInputStream fis = new FileInputStream("./io/src/primary/text/person.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Person per = (Person)ois.readObject();
        System.out.println(per);
        ois.close();
        fis.close();
    }

    /**
     * 拷贝文件
     *
     * @param source 源文件
     * @param dest   目的文件
     */
    private static void copyFileUsingChannel(File source, File dest) throws IOException {
        FileChannel sourceChannel = null;
        FileChannel destChannel = null;
        try {
            sourceChannel = new FileInputStream(source).getChannel();
            destChannel = new FileOutputStream(dest).getChannel();
            destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
        } finally {
            sourceChannel.close();
            destChannel.close();
        }
    }

    /**
     * 拷贝文件
     *
     * @param source 源文件
     * @param dest   目的文件
     */
    private static void copyFileUsingStream(File source, File dest) throws IOException {
        InputStream is = null;
        OutputStream os = null;
        try {
            is = new FileInputStream(source);
            os = new FileOutputStream(dest);
            byte[] buffer = new byte[1024];
            int length = 0;
            while ((length = is.read(buffer)) > 0)
                os.write(buffer, 0, length);
        } finally {
            is.close();
            os.close();
        }
    }

    /**
     * note
     * <p>
     * 1. 指定文件路径，最好使用系统属性 file.separator（除此之外的属性还有 user.dir）或者 File里提供的分隔静态常量，这样可以兼容不同系统
     * 2. 创建文件、删除文件等操作最后都是调用的文件系统的API，但这个文件系统的调用是没有公开的 @see java.io.WinNTFileSystem
     * 3. 不会自动创建没有的文件夹
     * </p>
     */
    private static void recognizeCreateFile() throws IOException {
        // 来自系统变量
        String fileSeparator = System.getProperty("file.separator");
        // 来自File里，JDK底层赋值 Windows -> \   Unix -> /
        String separatorFromFile = File.separator;
        // 跟楼上一样，不过这是char
        char separatorcharFromFile = File.separatorChar;
        // 来自File里，JDK底层赋值 Windows -> ;     Unix -> :
        String pathSeparatorFromFile = File.pathSeparator;
        // 跟楼上一样，不过这是char
        char pathSeparatorcharFromFile = File.pathSeparatorChar;
        // 1. 创建文件
        // 1.1 绝对路径创建文件
        String absoluteFilePath = fileSeparator + "backproject" + fileSeparator + "mix-code-learn" + fileSeparator + "io" + fileSeparator + "src" + fileSeparator + "primary" + fileSeparator + "text"
                + fileSeparator + "卡莎.txt";
        File file = new File(absoluteFilePath);
        if (file.createNewFile())
            System.out.println(absoluteFilePath + "File Created");
        else
            System.out.println("File " + absoluteFilePath + " already exists");

        // 1.2 相对路径创建文件
        String relativePath = "io" + fileSeparator + "src" + fileSeparator + "primary" + fileSeparator + "text"
                + fileSeparator + "虚空之女.txt";
        file = new File(relativePath);
        if (file.createNewFile())
            System.out.println(relativePath + " File Created in Project root directory");
        else
            System.out.println("File " + relativePath + " already exists in the project root directory");


    }


}
